﻿// --------------------------------------------------------------------------------------------------------------------
// <copyright file="Form.cs" company="Marcelo Roca">
//   Copyright (c) 2011 Marcelo Roca. All rights reserved.
// </copyright>
// <author name="Marcelo Roca" email="marce@marceloroca.com"/>
// <license>
//   This program is free software: you can redistribute it and/or modify
//   it under the terms of the GNU General Public License as published by
//   the Free Software Foundation, either version 3 of the License, or
//   (at your option) any later version.
// 
//   This program is distributed in the hope that it will be useful,
//   but WITHOUT ANY WARRANTY; without even the implied warranty of
//   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
//   GNU General Public License for more details.
//
//   You should have received a copy of the GNU General Public License
//   along with this program.  If not, see http://www.gnu.org/licenses/.
// </license>
// --------------------------------------------------------------------------------------------------------------------

namespace GameKits.Unity.GuiForms.Forms
{
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using Controls;
    using UnityEngine;

    public abstract class Form : MonoBehaviour, IContainer
    {
        #region Private Variables

        /// <summary>
        /// La liswta de ventanas
        /// </summary>
        private static readonly HashSet<Form> FormList = new HashSet<Form>();

        /// <summary>
        /// La colección de controles
        /// </summary>
        private readonly Collection<FixedControl> collection = new Collection<FixedControl>();

        /// <summary>
        /// Comandos que se le han asignado al formulario
        /// </summary>
        private readonly Queue<FormCommand> commandQueue = new Queue<FormCommand>();

        /// <summary>
        /// El contador de ventanas
        /// </summary>
        private static int WindowsCount;

        /// <summary>
        /// Especifrica si el contenedor ha sido inicializado
        /// </summary>
        private bool initialized;

        /// <summary>
        /// Indica si el formulario está habilitado
        /// </summary>
        private bool isEnabled = true;

        /// <summary>
        /// The guiskin
        /// </summary>
        private GUISkin skin;

        /// <summary>
        /// El comando que se está ejecutando actualmente
        /// </summary>
        private FormCommand currentCommand;

        /// <summary>
        /// El color del GUI
        /// </summary>
        private RgbColor color = new RgbColor(1, 1, 1);

        /// <summary>
        /// el color que estamos usando actualmente
        /// </summary>
        private Color currentColor = new Color(1, 1, 1, 1);

        /// <summary>
        /// la opacidad del contraol (transparencia)
        /// </summary>
        private float opacity = 1;

        /// <summary>
        /// indica si se muestra o no el border de la ventana
        /// </summary>
        private bool showBorder = true;

        /// <summary>
        /// El id de esta ventana
        /// </summary>
        private int windowId;

        /// <summary>
        /// la posición y tamaño del window
        /// </summary>
        private Rect windowRect;

        /// <summary>
        /// Indica que el formulario está deshabilitado internamente
        /// </summary>
        private bool isDisabled;

        /// <summary>
        /// indica si el formulario fue enviado al frente
        /// </summary>
        private bool sendToFront;

        #endregion

        #region Events

        /// <summary>
        /// Occurs when the form is hidden. 
        /// </summary>
        public event EventHandler Hidden;

        /// <summary>
        /// Occurs whenever the form is first displayed.
        /// </summary>
        public event EventHandler Shown;

        /// <summary>
        /// Occurs when the form is closed. 
        /// </summary>
        public event EventHandler Closed;

        /// <summary>
        /// Se ejecuta este evento cuando se repinta el GUI
        /// </summary>
        public event EventHandler Repainted;

        #endregion

        #region Properties

        /// <summary>
        /// Gets or sets a value indicating whether IsTopMost.
        /// </summary>
        public bool IsTopMost
        {
            get;
            set;
        }

        /// <summary>
        /// Gets or sets a value indicating whether IsEnabled.
        /// </summary>
        public bool IsEnabled
        {
            get { return !this.isDisabled && this.isEnabled; }
            set { this.isEnabled = value; }
        }

        /// <summary>
        /// Gets or sets Skin.
        /// </summary>
        public GUISkin Skin
        {
            get
            {
                return this.skin;
            }

            set
            {
                if (this.skin != value)
                {
                    this.skin = value;
                    this.IsDirty = true;
                }
            }
        }

        /// <summary>
        /// Gets or sets BackGround.
        /// </summary>
        public Texture BackGround
        {
            get;
            set;
        }

        /// <summary>
        /// Gets or sets a value indicating whether IsBorderLess.
        /// </summary>
        public bool IsBorderLess
        {
            get;
            set;
        }

        /// <summary>
        /// Gets or sets a value indicating whether IsDraggable.
        /// </summary>
        public bool IsDraggable
        {
            get;
            set;
        }

        /// <summary>
        /// Gets a value indicating whether IsVisible.
        /// </summary>
        public bool IsVisible
        {
            get;
            internal set;
        }

        /// <summary>
        /// Gets or sets GuiColor.
        /// </summary>
        public RgbColor Color
        {
            get
            {
                return this.color;
            }

            set
            {
                if (this.color != value)
                {
                    this.color = value;
                    this.currentColor = this.Color.ToColor(this.Opacity);
                }
            }
        }

        /// <summary>
        /// Gets or sets Opacity.
        /// </summary>
        public float Opacity
        {
            get
            {
                return this.opacity;
            }

            set
            {
                if (this.opacity != value)
                {
                    this.opacity = value;
                    this.currentColor = this.Color.ToColor(this.Opacity);
                }
            }
        }

        /// <summary>
        /// Gets or sets Height.
        /// </summary>
        public Rect WindowRect
        {
            get
            {
                return this.windowRect;
            }

            set
            {
                if (value.width != this.windowRect.width || value.height != this.windowRect.height)
                {
                    this.IsDirty = true;
                }

                this.windowRect = value;
            }
        }

        /// <summary>
        /// Gets or sets a value indicating whether ShowBorder.
        /// </summary>
        public bool ShowBorder
        {
            get { return this.showBorder; }
            set { this.showBorder = value; }
        }

        /// <summary>
        /// Gets or sets Title.
        /// </summary>
        public string Title
        {
            get;
            set;
        }

        /// <summary>
        /// Gets or sets a value indicating whether TopMost.
        /// </summary>
        public bool TopMost
        {
            get;
            set;
        }

        /// <summary>
        /// Gets or sets a value indicating whether Deleted.
        /// </summary>
        internal bool Deleted
        {
            get;
            set;
        }

        /// <summary>
        /// Gets or sets a value indicating whether IsDirty.
        /// </summary>
        protected bool IsDirty
        {
            get;
            set;
        }

        #endregion

        #region Collection Management

        /// <summary>
        /// Adicionamos un control a la lista
        /// </summary>
        /// <param name="control">
        /// The control.
        /// </param>
        public void Add(FixedControl control)
        {
            control.Parent = this;
            this.collection.Add(control);
        }

        /// <summary>
        /// Adicionamos varios controles a la lista
        /// </summary>
        /// <param name="controls">
        /// The control.
        /// </param>
        public void Add(params FixedControl[] controls)
        {
            foreach (var item in controls)
            {
                item.Parent = this;
                this.collection.Add(item);
            }
        }

        /// <summary>
        /// Quitamos un control de la lista
        /// </summary>
        /// <param name="control">
        /// The control.
        /// </param>
        /// <returns>
        /// true si existía el control
        /// </returns>
        public bool Remove(FixedControl control)
        {
            return this.collection.Remove(control);
        }

        /// <summary>
        /// Verifica si existe un control en la lista
        /// </summary>
        /// <param name="control">
        /// The control.
        /// </param>
        /// <returns>
        /// true si el control existe
        /// </returns>
        public bool Contains(FixedControl control)
        {
            return this.collection.Contains(control);
        }

        /// <summary>
        /// Elimina todos los controles
        /// </summary>
        public void Clear()
        {
            this.collection.Clear();
        }

        #endregion

        #region Form Methods

        /// <summary>
        /// Cierra el formulario
        /// </summary>
        /// <param name="formCommands">
        /// The form Commands.
        /// </param>
        public void Close(params FormCommand[] formCommands)
        {
            this.isDisabled = true;

            foreach (var command in formCommands.Where(command => !(command is FormClose)))
            {
                this.commandQueue.Enqueue(this.SetParent(command));
            }

            this.Close();
        }

        /// <summary>
        /// Cierra el formulario
        /// </summary>
        public void Close()
        {
            this.isDisabled = true;

            var cmd = this.SetParent(new FormClose());
            cmd.Executed += delegate { this.DoCloseEvent(); };
            this.commandQueue.Enqueue(cmd);
        }

        /// <summary>
        /// Muestra el formulario
        /// </summary>
        public void Hide()
        {
            if (!this.IsVisible)
            {
                return;
            }

            this.isDisabled = true;

            var cmd = this.SetParent(new FormHide());
            cmd.Executed += delegate { this.DoHideEvent(); };
            this.commandQueue.Enqueue(cmd);
        }

        /// <summary>
        /// Muestra el formulario
        /// </summary>
        /// <param name="formCommands">
        /// The form Commands.
        /// </param>
        public void Hide(params FormCommand[] formCommands)
        {
            if (!this.IsVisible)
            {
                return;
            }

            this.isDisabled = true;

            var cmd = this.SetParent(new FormHide());
            cmd.Executed += delegate { this.DoHideEvent(); };

            if (formCommands == null || formCommands.Length == 0)
            {
                this.commandQueue.Enqueue(cmd);
            }
            else
            {
                foreach (var command in formCommands.Where(command => !(command is FormHide)))
                {
                    this.commandQueue.Enqueue(this.SetParent(command));
                }

                this.commandQueue.Enqueue(cmd);
            }
        }


        /// <summary>
        /// Mover el formulario a otra posición
        /// </summary>
        /// <param name="speed">
        /// The speed (pixel por segundo).
        /// </param>
        /// <param name="position">
        /// La última posición.
        /// </param>
        public void Move(float speed, Vector2 position)
        {
            throw new NotImplementedException();
        }

        /// <summary>
        /// Manda el formulario al ffrente
        /// </summary>
        public void SendToFront()
        {
            this.sendToFront = true;
        }

        /// <summary>
        /// Muestra el formulario
        /// </summary>
        /// <param name="formCommands">
        /// The form Commands.
        /// </param>
        public void Show(params FormCommand[] formCommands)
        {
            if (this.IsVisible)
            {
                return;
            }

            this.isDisabled = true;

            var cmd = this.SetParent(new FormShow());
            cmd.Executed += delegate { this.DoShowEvent(); };

            if (formCommands == null || formCommands.Length == 0)
            {
                this.commandQueue.Enqueue(cmd);
            }
            else
            {
                foreach (var command in formCommands.Where(command => !(command is FormShow)))
                {
                    this.commandQueue.Enqueue(this.SetParent(command));
                }

                this.commandQueue.Enqueue(cmd);
            }
        }

        /// <summary>
        /// Muestra el formulario
        /// </summary>
        public void Show()
        {
            if (this.IsVisible)
            {
                return;
            }

            this.isDisabled = true;

            var cmd = this.SetParent(new FormShow());
            cmd.Executed += delegate { this.DoShowEvent(); };
            this.commandQueue.Enqueue(cmd);
        }

        #endregion

        #region Unity MonoBehaviour

        /// <summary>
        /// Implementa el MonoBehaviour.OnGUI
        /// </summary>
        public void OnGUI()
        {
            if (this.Deleted)
            {
                DestroyImmediate(this);
                return;
            }

            if (!this.initialized)
            {
                this.windowId = WindowsCount++;
                FormList.Add(this);

                this.Initialize();
                this.initialized = true;
            }

            var backupMatrixEffect = GUI.matrix;

            GUI.enabled = this.IsEnabled;

            if (this.currentCommand == null && this.commandQueue.Count > 0)
            {
                this.currentCommand = this.commandQueue.Dequeue();
            }

            if (this.currentCommand != null)
            {
                if (this.currentCommand.Execute())
                {
                    this.currentCommand = null;
                }
            }

            if (!this.IsVisible)
            {
                return;
            }

            GUI.skin = this.Skin;
            GUI.color = this.currentColor;

            this.DoLayout();

            // mostramos la ventana
            this.windowRect = GUI.Window(this.windowId, this.WindowRect, this.DoWindow, this.Title);

            GUI.matrix = backupMatrixEffect;


            if (this.TopMost)
            {
                GUI.BringWindowToFront(this.windowId);
            }
            else
            {
                if (this.sendToFront)
                {
                    GUI.BringWindowToFront(this.windowId);
                    this.sendToFront = false;
                }
            }

            this.DoRepaintEvent();
        }

        #endregion

        #region Virtual Methods

        /// <summary>
        /// Se ejecuta cuando el formulairo se muestra
        /// </summary>
        public virtual void OnShow()
        {
        }

        /// <summary>
        /// Se ejecuta cuando el form se esconde
        /// </summary>
        public virtual void OnHide()
        {
        }

        /// <summary>
        /// Se ejecuta cuando el form se cierra
        /// </summary>
        public virtual void OnClose()
        {
        }

        /// <summary>
        /// Impelemntar para realizar los calculos del formulario
        /// </summary>
        protected virtual void OnLayout()
        {
        }

        /// <summary>
        /// Se llama despues de cada repaint
        /// </summary>
        protected virtual void OnRepaint()
        {
        }

        /// <summary>
        /// Inicializa los contrles
        /// </summary>
        protected abstract void Initialize();

        #endregion

        #region Internal Methods

        /// <summary>
        /// Calula la posición del formulario utilizando el tamaño del screen
        /// </summary>
        /// <param name="startPosition">
        /// El alineamiento del formulario.
        /// </param>
        /// <param name="width">
        /// El ancho del formlario.
        /// </param>
        /// <param name="height">
        /// El alto del formlario.
        /// </param>
        /// <returns>
        /// La posición del formulario calculada
        /// </returns>
        protected static Rect CalculatePosition(EffectPivots startPosition, float width, float height)
        {
            float x;
            float y;

            switch (startPosition)
            {
                case EffectPivots.TopLeft:
                    x = 0;
                    y = 0;
                    break;

                case EffectPivots.TopMiddle:
                    x = (Screen.width - width) / 2;
                    y = 0;
                    break;

                case EffectPivots.TopRight:
                    x = Screen.width - width;
                    y = 0;
                    break;

                case EffectPivots.MiddleLeft:
                    x = 0;
                    y = (Screen.height - height) / 2;
                    break;

                case EffectPivots.MiddleRight:
                    x = Screen.width - width;
                    y = (Screen.height - height) / 2;
                    break;

                case EffectPivots.DownLeft:
                    x = 0;
                    y = Screen.height - height;
                    break;

                case EffectPivots.DownMiddle:
                    x = (Screen.width - width) / 2;
                    y = Screen.height - height;
                    break;

                case EffectPivots.DownRight:
                    x = Screen.width - width;
                    y = Screen.height - height;
                    break;

                default:
                    x = (Screen.width - width) / 2;
                    y = (Screen.height - height) / 2;
                    break;
            }

            return new Rect(x, y, width, height);
        }

        /// <summary>
        /// Llamaa a los evcentos correspondientes despues de esconder el formulario
        /// </summary>
        private void DoHideEvent()
        {
            this.isDisabled = false;

            this.OnHide();

            if (this.Hidden != null)
            {
                this.Hidden(this, new EventArgs());
            }
        }

        /// <summary>
        /// Llama a los eventos correspondietes despues de mostrar el formulario
        /// </summary>
        private void DoShowEvent()
        {
            this.isDisabled = false;

            this.OnShow();

            if (this.Shown != null)
            {
                this.Shown(this, new EventArgs());
            }
        }

        /// <summary>
        /// Llama a los eventos correspondiente despues de cerrar el formulario
        /// </summary>
        private void DoCloseEvent()
        {
            this.OnClose();

            if (this.Closed != null)
            {
                this.Closed(this, new EventArgs());
            }
        }

        /// <summary>
        /// Realiza los calculos del tamañio del form
        /// </summary>
        private void DoLayout()
        {
            if (this.IsDirty)
            {
                this.OnLayout();
                this.IsDirty = false;
            }
        }

        /// <summary>
        /// Realiza los calculos del tamañio del form
        /// </summary>
        private void DoRepaintEvent()
        {
            this.OnRepaint();

            if (this.Repainted != null)
            {
                this.Repainted(this, new EventArgs());
            }
        }

        /// <summary>
        /// Vincula un nuevo comando de formulari
        /// </summary>
        /// <param name="command">
        /// The command.
        /// </param>
        /// <returns>
        /// El comando vinculado con el frmulario
        /// </returns>
        private FormCommand SetParent(FormCommand command)
        {
            command.SetParent(this);
            return command;
        }

        /// <summary>
        /// Mostramos el contenido interior del formulario
        /// </summary>
        /// <param name="id">
        /// The id dela ventana.
        /// </param>
        private void DoWindow(int id)
        {
            if (!this.ShowBorder)
            {
                GUI.Label(new Rect(0, 0, this.WindowRect.width, this.WindowRect.height), this.BackGround);
            }

            this.collection.DrawInnercontrols();

            if (this.IsDraggable)
            {
                GUI.DragWindow(new Rect(0, 0, 10000, 20));
            }
        }

        #endregion
    }
}
